# coding: utf-8

import os
import os.path
from sqlalchemy.sql.base import Executable
import win32file
import win32con
import json
import time
import signal
import requests
from requests.exceptions import HTTPError
from xls import xls_file_to_json
import toml

# 调用(云函数(实质就是api接口)
def callAliyunFunc(url, json_from_client):
    headers = {
        # "Host": "1825138619804807.cn-zhangjiakou.fc.aliyuncs.com",
        "Content-Type": "application/json",
        "Cache-Control": "no-cache",
    }
    # 序列化时对中文默认使用的ascii编码.想输出真正的中文需要指定ensure_ascii=False
    json_str = json.dumps(json_from_client, ensure_ascii=True)
    # print(json_str)
    # 发送post请求
    response = requests.post(url, data=json_str, headers=headers)
    # If the response was successful, no Exception will be raised
    response.raise_for_status()
    return response


# 上传传感器数据(调用阿里云函数接口)
def upload_data(url, sensor_datas, max_retries=3):
    # 最大重试次数
    ret = False
    for _ in range(max_retries):
        try:
            # 开始计时
            start = time.process_time()
            timestamp = time.strftime("%Y/%m/%d %H:%M:%S")
            print("[%s]上传%d个传感器数据" % (timestamp, len(sensor_datas)))
            for x in sensor_datas:
                print(x, end=",")
                print()
            # print(sensor_datas)
            # 传感器数据上传接口(阿里云)
            # url = "http://127.0.0.1:9004/api/monitor/upload"
            # url = "https://1825138619804807.cn-zhangjiakou.fc.aliyuncs.com/2016-08-15/proxy/jl/api/monitor/upload"
            response = callAliyunFunc(url, sensor_datas)
            # 结束计时
            end = time.process_time()
            print("状态码:%d 云函数耗时:%f s" % (response.status_code, end - start))
            # 提取返回结果
            results = json.loads(response.content)
            if not results:
                raise Exception("返回的结果不是json格式")

            if "status" not in results:
                raise Exception("返回json中缺少[status]字段")

            if results.get("status", 0) == 0:
                raise Exception(results.get("msg", "lack 'msg' field"))

            ret = True
            break
        except HTTPError as http_err:
            # raise Exception(f"HTTP错误: {http_err}")
            print(f"[upload_data]HTTP错误: {http_err}")
            time.sleep(5)
            continue
        except Exception as err:
            # raise Exception(f"其它错误: {err}")
            print(f"[upload_data]其它错误: {err}")
            time.sleep(5)
            continue
    if not ret:
        # raise Exception("[upload_data]超过重试次数(max_retries=%d)" % max_retries)
        print("上传失败, 超过重试次数(max_retries=%d)" % max_retries)
    else:
        print("上传成功")
    print()
    print()


# 读取json文件,返回词典对象
def read_json_file(json_file, encoding="utf-8"):
    root = None
    with open(json_file, "r", encoding=encoding) as f:
        root = json.load(f)
    return root


# 将 2020/10/9 15:50:42 格式字符串 转换成 unix timestamp
def makeTimestamp(str_time):
    try:
        return int(
            time.mktime(time.strptime(str_time.replace("/", "-"), "%Y-%m-%d %H:%M:%S"))
        )
    except:
        return time.time()


def to_value(value_type, val):
    try:
        if value_type == "float":
            return float(val)
        elif value_type == "int":
            return int(val)
        elif value_type == "string":
            return str(val)
    except:
        return None


def _build_sensor_data(datas, sensor_devices, sensor_catalogs):
    # 提取传感器数据
    sensor_id, sensor_value, sensor_catalog = datas[0], datas[1], datas[2]
    _sensor_id = int(sensor_id)
    # print(sensor_id, type(sensor_id), _sensor_id, type(_sensor_id))
    if _sensor_id not in sensor_devices:
        return None
    if sensor_catalog not in sensor_catalogs:
        return None
    sensor_value = to_value(sensor_catalogs[sensor_catalog]["value_type"], sensor_value)
    if sensor_value == None:
        return None
    # print(sensor_devices[_sensor_id], sensor_catalogs[sensor_catalog])
    # HACK 排除风速0的情况
    if sensor_catalog == "风速" and sensor_value == 0:
        return None
    return {
        "uuid": sensor_devices[_sensor_id],
        "type": sensor_catalogs[sensor_catalog]["sensor_type"],
        "value": sensor_value,
        "metric": sensor_catalogs[sensor_catalog]["metric"],
    }


# 一次性读取文件中的所有传感器数据
def _read_sensor_file(f, sensor_devices, sensor_catalogs):
    sensor_datas = []
    timestamp = None
    num = 0
    for line in f:
        num += 1
        _line = line.strip()
        if num == 1:
            # 第1行中提取unix timestamp
            timestamp = makeTimestamp(_line[5:-14])
        elif num == 2:
            # 第2行跳过
            continue
        else:
            _datas = _line.split()
            if len(_datas) < 3:
                continue
            # print(_datas)
            sensor_data = _build_sensor_data(_datas, sensor_devices, sensor_catalogs)
            # print(sensor_data)
            if sensor_data == None:
                continue
            # 统一加上unix时间戳
            sensor_data["time"] = timestamp
            # 记录已提取的传感器数据
            sensor_datas.append(sensor_data)
    return sensor_datas


# 读取传感器数据文件, 并返回符合API接口上传要求的数据
def read_sensor_file(filename, sensor_devices, sensor_catalogs):
    sensor_datas = None
    try:
        with open(filename, "r", encoding="utf-8") as f:
            # 读取文件, 返回符合API接口上传要求的数据
            sensor_datas = _read_sensor_file(f, sensor_devices, sensor_catalogs)
    except Exception as e:
        print(e)
        sensor_datas = None
    return sensor_datas


def read_and_upload_callback(filename, args):
    # 元组拆包(unpack)
    url, sensor_devices, sensor_catalogs, max_retries = args
    # 得到符号API接口上传要求的数据
    sensor_datas = read_sensor_file(filename, sensor_devices, sensor_catalogs)
    if sensor_datas != None:
        # 上传数据
        upload_data(url, sensor_datas, max_retries)


ACTIONS = {
    1: "Created",
    2: "Deleted",
    3: "Updated",
    4: "Renamed from something",
    5: "Renamed to something",
}

# https://www.cnblogs.com/lcamry/p/8392376.html
# 调用win32 API监控文件状态(删除、新建、更新)
# 当有文件被更新之后, 调用file_update_func回调函数, 传入args参数
def _monitor_file_and_upload_data(path, filename, timeout, file_update_func, args):
    hDir = win32file.CreateFile(
        path,
        win32con.GENERIC_READ | win32con.GENERIC_WRITE,
        win32con.FILE_SHARE_READ | win32con.FILE_SHARE_WRITE,
        None,
        win32con.OPEN_EXISTING,
        win32con.FILE_FLAG_BACKUP_SEMANTICS,
        None,
    )
    while True:
        results = win32file.ReadDirectoryChangesW(
            hDir,  # handle: Handle to the directory to be monitored. This directory must be opened with the FILE_LIST_DIRECTORY access right.
            1024,  # size: Size of the buffer to allocate for the results.
            False,  # bWatchSubtree: Specifies whether the ReadDirectoryChangesW function will monitor the directory or the directory tree.
            win32con.FILE_NOTIFY_CHANGE_FILE_NAME
            | win32con.FILE_NOTIFY_CHANGE_DIR_NAME
            | win32con.FILE_NOTIFY_CHANGE_ATTRIBUTES
            | win32con.FILE_NOTIFY_CHANGE_SIZE
            | win32con.FILE_NOTIFY_CHANGE_LAST_WRITE
            | win32con.FILE_NOTIFY_CHANGE_SECURITY,
            None,
            None,
        )
        # new_files = set()
        update_files = set()
        for _action, _file in results:
            full_filename = os.path.join(path, _file)
            # print(full_filename, _file, _action)

            # 新增文件
            if _file == filename:
                # if _action == 1:
                #     new_files.add(full_filename)
                if _action == 1 or _action == 2 or _action == 3:
                    update_files.add(full_filename)

        # 挂起1s(给传感器update文件留一点缓冲时间)
        if timeout > 0:
            time.sleep(timeout)

        # 文件更新后执行相关操作
        # 这里使用回调file_update_func来完成
        for full_filename in update_files:
            print(f"检测{full_filename}文件发生变化, action:{_action}")
            file_update_func(full_filename, args)


def monitor_file_and_upload_data(
    path, filename, timeout, url, sensor_devices, sensor_catalogs, max_retries
):
    # 调用win32 API监控文件状态(删除、新建、更新)
    # 当有文件被更新之后, 调用read_and_upload_callback回调函数, 传入sensor_devices, sensor_catalogs参数
    _monitor_file_and_upload_data(
        path,
        filename,
        timeout,
        read_and_upload_callback,
        (url, sensor_devices, sensor_catalogs, max_retries),
    )


def main():
    # 读取传感器配置表格
    config = xls_file_to_json("./sensor_config.xls")
    # print(config)
    sensor_catalogs = dict(
        [
            (
                obj["name"],
                {
                    "sensor_type": obj["sensor_type"],
                    "value_type": obj["value_type"],
                    "metric": obj["metric"],
                },
            )
            for obj in config["catalogs"]
        ]
    )
    # print(sensor_catalogs)
    # sensor_devices = { id -> uuid }
    sensor_devices = dict(
        [(sensor["id"], sensor["uuid"]) for sensor in config["devices"]]
    )
    # print(sensor_devices)
    if not os.path.exists(config["monitor"]["path"]):
        os.makedirs(config["monitor"]["path"])

    api_config = toml.load("./api_config.toml")
    url = f"http://{api_config['sensor']['url']}/{api_config['sensor']['upload_api']}"
    print("url:", url)
    monitor_file_and_upload_data(
        config["monitor"]["path"],
        config["monitor"]["filename"],
        config["monitor"]["timeout"],
        url,
        sensor_devices,
        sensor_catalogs,
        api_config["sensor"]["max_retries"],
    )


def register_exit_signal():
    def exit(signum, frame):
        # print("You choose to stop me.")
        os._exit(0)

    signal.signal(signal.SIGINT, exit)
    signal.signal(signal.SIGTERM, exit)


if __name__ == "__main__":
    register_exit_signal()
    main()
